#pragma once

#include "byteStream.hpp"
#include "epollGuard.hpp"
#include "fileGuard.hpp"
#include "uartParam.hpp"
#include <errno.h>
#include <fcntl.h>
#include <map>
#include <memory>
#include <mutex>
#include <string.h>
#include <string>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>

class Uart {
private:
    inline static std::map<std::string, std::unique_ptr<std::mutex>> _mutexTable;
    inline static std::mutex _mutex;
    inline static int _epollFd = -1;

    // std::mutex _posixMutex;
    int _fd;
    UartParam _param;

private:
    static std::mutex &getMutex(const std::string &port) {
        std::lock_guard<std::mutex> lock(_mutex);

        auto it = _mutexTable.find(port);
        if (it == _mutexTable.end()) {
            it = _mutexTable.emplace(std::make_pair(port, std::make_unique<std::mutex>())).first;
        }

        return *it->second;
    }

    static int open(const UartParam &p);

public:
    bool open();
    bool open(const std::string &p, int baudrate, int databits, int stopbits, char parity);
    bool open(const std::string &p, int baudrate, int databits, int stopbits, const std::string &parity);
    int fd() const { return _fd; };
    bool write(const ByteStream &b);
    ByteStream read(std::size_t size);
    ByteStream read(std::size_t size, int ms);
    bool close();

    bool reset(const std::string &p, int baudrate, int databits, int stopbits, char parity);
    bool reset(const std::string &p, int baudrate, int databits, int stopbits, const std::string &parity);

    bool setPort(const std::string &port);
    bool setBaudrate(int baudrate);
    bool setDatabits(int databits);
    bool setStopbits(int stopbits);
    bool setParity(char parity);
    bool setParity(const std::string &parity);

    // static ByteStream transmit(const UartParam &p, const ByteStream &data, std::size_t size = 256);
    static ByteStream transmit(const UartParam &p, const ByteStream &data, int ms, std::size_t size = 256);

    bool isOpen() const { return _fd > 0; }

public:
    Uart()
        : _fd(-1){};
    Uart(const std::string &p, int baudrate, int databits, int stopbits, const std::string &parity)
        : _param(p, baudrate, databits, stopbits, parity){};
    Uart(const std::string &p, int baudrate, int databits, int stopbits, char parity)
        : _param(p, baudrate, databits, stopbits, parity){};
};

inline int Uart::open(const UartParam &p) {
    int flags = O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL | O_CLOEXEC;
    auto fd = ::open(p.port(), flags);
    if (fd < 0) {
        // throw UartParamError("open file failed, filename : " + std::string(p.port()) + " errno " + strerror(errno));
        std::cerr << "open file failed, filename : " << p.port() << " errno : " << strerror(errno) << std::endl;
        return fd;
    }

    struct termios tios {};
    if ((cfsetispeed(&tios, p.baudrate()) < 0) || (cfsetospeed(&tios, p.baudrate()) < 0)) {
        ::close(fd);
        return -1;
    }

    tios.c_cflag |= (CREAD | CLOCAL);
    tios.c_cflag &= ~CSIZE;

    if (p.databits() == 5) {
        tios.c_cflag |= CS5;
    } else if (p.databits() == 6) {
        tios.c_cflag |= CS6;
    } else if (p.databits() == 7) {
        tios.c_cflag |= CS7;
    } else if (p.databits() == 8) {
        tios.c_cflag |= CS8;
    }

    if (p.stopbits() == 1) {
        tios.c_cflag &= ~CSTOPB;
    } else if (p.stopbits() == 2) {
        tios.c_cflag |= CSTOPB;
    }

    if (p.parity() == 'E') {
        tios.c_cflag |= PARENB;
        tios.c_cflag &= ~PARODD;
    } else if (p.parity() == 'N') {
        tios.c_cflag &= ~PARENB;
    } else if (p.parity() == 'O') {
        tios.c_cflag |= PARENB;
        tios.c_cflag |= PARODD;
    }

    tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

    if (p.parity() == 'N') {
        tios.c_iflag &= ~INPCK;
    } else {
        tios.c_iflag |= INPCK;
    }

    tios.c_iflag &= ~(IXON | IXOFF | IXANY);
    tios.c_oflag &= ~OPOST;

    tios.c_cc[VMIN] = 0;
    tios.c_cc[VTIME] = 0;

    if (tcsetattr(fd, TCSANOW, &tios) < 0) {
        ::close(fd);
        return -1;
    }

    return fd;
}

inline bool Uart::open() {
    return (_fd = open(_param)) > 0;
}

inline bool Uart::open(const std::string &p, int baudrate, int databits, int stopbits, char parity) {
    _param = UartParam(p, baudrate, databits, stopbits, parity);
    return open();
}

inline bool Uart::open(const std::string &p, int baudrate, int databits, int stopbits, const std::string &parity) {
    _param = UartParam(p, baudrate, databits, stopbits, parity);
    return open();
}

inline bool Uart::write(const ByteStream &b) {
    if (!isOpen()) {
        return false;
    }

    return ::write(_fd, b.data(), b.length()) == b.length();
}

inline ByteStream Uart::read(std::size_t size) {
    if (!isOpen()) {
        return ByteStream{};
    }

    fd_set readFds;
    FD_ZERO(&readFds);
    FD_SET(_fd, &readFds);

    auto ret = select(_fd + 1, &readFds, nullptr, nullptr, nullptr); /* will block */
    if (ret <= 0) {
        return ByteStream{};
    }

    auto buffer = std::make_unique<uint8_t[]>(size);
    int nBytes = 0;

    if (FD_ISSET(_fd, &readFds)) {
        nBytes = ::read(_fd, buffer.get(), size);
        if (nBytes <= 0) {
            return ByteStream{};
        }
    }

    return ByteStream(buffer.get(), nBytes);
}

inline ByteStream Uart::read(std::size_t size, int ms) {
    if (!isOpen()) {
        return ByteStream{};
    }

    fd_set readFds;
    FD_ZERO(&readFds);
    FD_SET(_fd, &readFds);

    timeval timeout = {
        .tv_sec = ms / 1000,
        .tv_usec = (ms % 1000) * 1000,
    };

    auto ret = select(_fd + 1, &readFds, nullptr, nullptr, &timeout); /* will block */
    if (ret <= 0) {
        return ByteStream{};
    }

    auto buffer = std::make_unique<uint8_t[]>(size);
    int nBytes = 0;

    if (FD_ISSET(_fd, &readFds)) {
        nBytes = ::read(_fd, buffer.get(), size);
        if (nBytes < 0) {
            return ByteStream{};
        }
    }

    return ByteStream(buffer.get(), nBytes);
}

inline bool Uart::close() {
    if (isOpen()) {
        ::close(_fd);
        _fd = -1;
    }
    return true;
}

inline bool Uart::reset(const std::string &p, int baudrate, int databits, int stopbits, char parity) {
    if (isOpen()) {
        close();
        return open(p, baudrate, databits, stopbits, parity);
    }

    _param = UartParam(p, baudrate, databits, stopbits, parity);
    return true;
}

inline bool Uart::reset(const std::string &p, int baudrate, int databits, int stopbits, const std::string &parity) {
    if (isOpen()) {
        close();
        return open(p, baudrate, databits, stopbits, parity);
    }

    _param = UartParam(p, baudrate, databits, stopbits, parity);
    return true;
}

inline bool Uart::setPort(const std::string &port) {
    _param.setPort(port);

    if (isOpen()) {
        close();
        return open();
    }

    return true;
}

inline bool Uart::setBaudrate(int baudrate) {
    _param.setBaudrate(baudrate);
    if (isOpen()) {
        close();
        return open();
    }

    return true;
}

inline bool Uart::setDatabits(int databits) {
    _param.setDatabits(databits);
    if (isOpen()) {
        close();
        return open();
    }

    return true;
}

inline bool Uart::setStopbits(int stopbits) {
    _param.setStopbits(stopbits);
    if (isOpen()) {
        close();
        return open();
    }

    return true;
}

inline bool Uart::setParity(char parity) {
    _param.setParity(parity);
    if (isOpen()) {
        close();
        return open();
    }

    return true;
}

inline bool Uart::setParity(const std::string &parity) {
    _param.setParity(parity);
    if (isOpen()) {
        close();
        return open();
    }

    return true;
}

// inline ByteStream Uart::transmit(const UartParam &p, const ByteStream &data, std::size_t size) {
//     std::unique_lock<std::mutex> lock(getMutex(p.port()));

//     auto fd = open(p);
//     if (fd < 0) {
//         return ByteStream{};
//     }

//     FileGuard fileGuard(fd);

//     if (_epollFd < 0 && (_epollFd = epoll_create1(0)) < 0) {
//         return ByteStream{};
//     }

//     EpollGuard epollGuard(_epollFd, fd);

//     auto buffer = std::make_unique<uint8_t[]>(size);
//     auto len = 0;

//     while (true) {
//         if (epoll_wait(_epollFd, epollGuard.event(), 1, -1) <= 0) {
//             break;
//         }

//         if ((epollGuard.event()->events & EPOLLERR) || (epollGuard.event()->events & EPOLLHUP)) {
//             std::cout << "epoll error!!!" << std::endl;
//             break;
//         }

//         auto nread = ::read(fd, buffer.get() + len, size - len);
//         tcdrain(fd);
//         tcflush(fd, TCIOFLUSH);
//         len += nread;
//     }

//     return ByteStream(buffer.get(), len);
// }

inline ByteStream Uart::transmit(const UartParam &p, const ByteStream &data, int ms, std::size_t size) {
    std::unique_lock<std::mutex> lock(getMutex(p.port()));

    auto fd = open(p);
    if (fd < 0) {
        return ByteStream{};
    }

    FileGuard fileGuard(fd);

    if (_epollFd < 0 && (_epollFd = epoll_create1(0)) < 0) {
        return ByteStream{};
    }

    EpollGuard epollGuard(_epollFd, fd);

    auto buffer = std::make_unique<uint8_t[]>(size);
    auto len = 0;

    if (::write(fd, data.data(), data.length()) != data.length()) {
        return ByteStream{};
    }

    // std::cout << "tx --> " << data << std::endl;

    while (true) {
        if (epoll_wait(_epollFd, epollGuard.event(), 1, len ? 50 : ms) <= 0) {
            break;
        }

        if ((epollGuard.event()->events & EPOLLERR) || (epollGuard.event()->events & EPOLLHUP)) {
            std::cerr << "epoll error!!!" << std::endl;
            break;
        }

        auto nread = ::read(fd, buffer.get() + len, size - len);
        tcdrain(fd);
        tcflush(fd, TCIOFLUSH);
        len += nread;
    }

    return ByteStream(buffer.get(), len);
}
